﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Baik.Interface.Framework;

namespace Baik
{
    public class Injector : IInjector
    {
        private InternalInjector _inject = new InternalInjector(); // simple depdency injection container that instancates all classes specifed in default constructor
        public Injector()
        {
            //this is self injection so that all componentents can  register sub components by being instanciated by this object
            Register<IInjector>().AsInstanceOf(this);
        }
        public IRegister Register(Type typ)
        {
            return new Reg() { Injector = this, InterfaceType = typ };
        }

        public void Run(IRegistrationScript rs)
        {
            rs.Register(this);
        }
        public void Add<TA, TC>()
        {
            _inject.Add<TA, TC>();
        }
        public void Add(Type abs, Type conc)
        {
            _inject.Add(abs, conc);
        }
        public void AddSingleton(Type abs, Type conc)
        {
            _inject.Add(abs, conc, InternalInjector.Record.LifeCycleEnumeration.Singleton);
        }

        public void AddInstance<TC>(Type interf, TC instance)
        {
            _inject.AddInstance(interf, typeof(TC), instance);
        }
        public TA Instanciate<TA>()
        {
            return _inject.Get<TA>();
        }
        public IRegister Register<TA>()
        {
            return new Reg() { Injector = this, InterfaceType = typeof(TA) };
        }

        public class Reg : IRegister
        {
            public Type InterfaceType { get; set; }
            public Injector Injector { get; set; }

            public void As<TC>()
            {
                Injector.Add(InterfaceType, typeof(TC));
            }

            public void As(Type typ)
            {
                Injector.Add(InterfaceType, typ);
            }

            public void AsSingletonOf<TC>()
            {
                Injector.AddSingleton(InterfaceType, typeof(TC));
            }

            public void AsSingletonOf(Type typ)
            {
                Injector.AddSingleton(InterfaceType, typ);
            }

            public void AsInstanceOf<TC>(TC instance)
            {
                Injector.AddInstance(InterfaceType, instance);
            }

        }

        public TA Instantiate<TA>()
        {
            return _inject.Get<TA>();
        }

        public object Instantiate(Type t)
        {
            return _inject.Get(t);
        }

        public TA Instantiate<TA>(object[] parms)
        {
            return _inject.Get<TA>(parms);
        }

        public object Instantiate(Type t, object[] parms)
        {
            return _inject.Get(t, parms);
        }
    }
 
    internal class InternalInjector 
    {
        private Dictionary<Type, Record> _dict = new Dictionary<Type, Record>();

        public T Get<T>(IEnumerable<object> parms)
        {
            return (T)Get(typeof(T), parms);
        }
        public object Get(Type t, IEnumerable<object> parms)
        {
            Record rtn;
            if (!_dict.TryGetValue(t, out rtn)) throw new TypeInitializationException("Failes to Create Type " + t.ToString() + " as there is No Registered Concrete for that Abstaract", new Exception("Record Not Found"));
            return rtn.GetInstance(parms);
        }
        public object Get(Type t) { return _dict[t].Instance; }

        private Record Find(Type t)
        {
            Record rtn = null;
            if (!_dict.TryGetValue(t, out rtn))
            {
                rtn = new Record(this);
                _dict.Add(t, rtn);
            }
            return rtn;
        }
        public T Get<T>()
        {
            return (T)_dict[typeof(T)].Instance;
        }

        public void Add(Type interf, Type conc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
        }
        public void AddMethod(Type interf, Type conc, MethodInfo method)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
            rtn.Method = method;

        }
        public void AddInstance(Type interf, Type conc, object instnc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.Singleton;
            rtn.Instance = instnc;
        }
        public void Add(Type interf, Type conc, Record.LifeCycleEnumeration lc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = lc;

        }
        public void Add<TI, TC>()
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;

        }
        public void Add<TI, TC>(MethodInfo method)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;

            rtn.Method = method;
        }
        public void Add<TI, TC>(Record.LifeCycleEnumeration lifeCycle)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = lifeCycle;
        }
        public void Add<TI, TC>(MethodInfo method, Record.LifeCycleEnumeration lifeCycle)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = lifeCycle;
            rtn.Method = method;
        }

        private object ReturnAndRemove(object itm, List<object> items)
        {
            items.Remove(itm);
            return itm;
        }

        internal class Record
        {
            private InternalInjector Injector { get; set; }

            public MethodInfo Method
            {
                get { return _method; }
                set
                {
                    _method = value;
                    UsesConstructor = (Method == null);
                }
            }

            private bool UsesConstructor = true;
            public enum LifeCycleEnumeration
            {
                Singleton,
                InstancePerCall
            }
            public LifeCycleEnumeration LifeCycle { get; set; }
            public Type Type { get; set; }
            private object _instance = null;
            private MethodInfo _method;

            public Record(InternalInjector injector)
            {
                Injector = injector;
            }
            public object CreateInstance(IEnumerable<object> parms)
            {

                var passedParameters = parms.ToArray();
                if (UsesConstructor)
                {
                    var constructorParameters = this.Type.GetConstructors()[0].GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
                    return constructorParameters.Length > 0 ? Activator.CreateInstance(Type, constructorParameters) : Activator.CreateInstance(Type);
                }
                else
                {
                    var methodParameters = this.Method.GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
                    return Method.Invoke(this, methodParameters);
                }
            }

            private object GetInstance(Type t, IEnumerable<object> parms)
            {
                var enumerable = parms as object[] ?? parms.ToArray();
                if (enumerable.Length > 0)
                {
                    var passedObject = enumerable.FirstOrDefault(p => p.GetType() == t);
                    if (passedObject == null)
                    {

                        return Injector.Get(t, enumerable);
                    }

                    else
                    {
                        return passedObject;
                    }
                }
                else
                {
                    return Injector.Get(t, enumerable);
                }
            }

            public object CreateInstance()
            {
                return CreateInstance(new object[] { });
            }

            public object GetInstance(IEnumerable<object> parms)
            {
                return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance(parms) : _instance ?? (_instance = CreateInstance(parms));
            }
            public object Instance
            {
                get { return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance() : _instance ?? (_instance = CreateInstance()); }
                set { _instance = value; }
            }

        }

    }
}
